"""请求处理(RequestHandler)基类"""

import logging

import aiomysql
import jwt
import tornado.escape
from jwt import InvalidSignatureError, ExpiredSignatureError
from tornado.web import RequestHandler

from config import (
    DEFAULT_FROM_EMAIL, DEFAULT_SUBJECT, MAIL_PARAMS, DEBUG,
    TOKEN_EXPIRED_TIME, COOKIE_EXPIRED_DATE, JWT_TOKEN_PREFIX
)
from utils import send_mail_async, fake_send_mail_async


class BaseHandler(RequestHandler):
    async def prepare(self):
        """
        获取请求体内容并确保前端传过来的是JSON数据
        判断用户是否提供认证token, 如果提供self.current_user不为None
        """
        self.request_data = {}
        # 这里使用startswith方法进行判断是因为，Content-Type中可能有charset='xx'
        is_json = self.request.headers.get(
            'Content-Type', '').startswith('application/json')
        if self.request.body and is_json:
            self.request_data = tornado.escape.json_decode(self.request.body)

        # 因为get_current_user方法不能是协程，我们在prepare方法中设置self.current_user
        # 每次请求方法执行之前执行prepare方法，从请求头Authorization中获取token信息，判断
        # 用户是登录用户
        if self.get_raw_token():
            self.current_user = await self.validate_token()
        else:
            self.current_user = None

    # 设置默认响应头响应类型为JSON
    def set_default_headers(self):
        self.set_header('Content-Type', 'application/json; charset="utf-8"')

    # 获取Application对象中的线程池
    @property
    def mysql_pool(self):
        return self.settings['mysql_pool']

    @property
    def redis_pool(self):
        return self.settings['redis_pool']

    async def execute(self, stmt, *params):
        async with self.mysql_pool.acquire() as conn:
            cursor = await conn.cursor()
            await cursor.execute(stmt, params)
            # 注意create_pool设置参数autocommit=False,我们需要主动提交才生效
            await conn.commit()

    async def fetchone(self, stmt, *params):
        async with self.mysql_pool.acquire() as conn:
            cursor = await conn.cursor(aiomysql.DictCursor)
            await cursor.execute(stmt, params)
            row = await cursor.fetchone()
            return row

    # 重写,提供定制错误信息
    def write_error(self, status_code, **kwargs):
        self.set_status(status_code)
        if 'exc_info' in kwargs:  # 系统抛的异常
            if status_code == 403:
                self.finish({'status': 1, 'errors': ['您需要登录才能访问此页面']})
            # 还有其他系统异常？？？
        else:
            self.finish(kwargs)

    async def send_mail(self, credentials, cookie_name, key, msg, token, text_type='html'):  # credentials->dict
        self.set_secure_cookie(cookie_name, key, expires_days=COOKIE_EXPIRED_DATE)

        try:
            with await self.redis_pool as redis_conn:
                # 键唯一？
                await redis_conn.execute('setex', key, TOKEN_EXPIRED_TIME, token)
        except Exception as e:
            logging.error(e)
            raise  # 使用的时候再捕获异常进行相应的处理

        try:
            if DEBUG:
                await fake_send_mail_async(  # 模拟发送邮件
                    sender=DEFAULT_FROM_EMAIL,
                    to=[credentials['email']],  # 列表
                    subject=DEFAULT_SUBJECT,
                    text=msg,
                    text_type=text_type,
                    **MAIL_PARAMS
                )
            else:
                await send_mail_async(  # 真实发送邮件
                    sender=DEFAULT_FROM_EMAIL,
                    to=[credentials['email']],
                    subject=DEFAULT_SUBJECT,
                    text=msg,
                    text_type=text_type,
                    **MAIL_PARAMS
                )
        except Exception as e:
            logging.error('发送激活邮件失败', str(e))
            raise  # 使用的时候再捕获异常进行相应的处理

    def get_raw_token(self):
        headers = self.request.headers
        raw_token = headers.get('Authorization', '')
        if not raw_token:
            return None  # 验证不通过
        return raw_token  # 'Bearer ....'

    async def validate_token(self):
        try:
            prefix, token = self.get_raw_token().split()
        except ValueError:
            return None
        else:
            if prefix == JWT_TOKEN_PREFIX:
                # 验证token
                try:
                    payload = jwt.decode(token.encode(), self.settings['cookie_secret'], algorithms=['HS256'])
                except (InvalidSignatureError, ExpiredSignatureError):
                    self.write_error(400, status=1, errors=['无效口令'])
                except Exception as e:
                    logging.error(e)
                    return None
                else:
                    username = payload['data']['username']
                    row = await self.fetchone('SELECT is_active, username FROM user WHERE username = %s', username)
                    if row and row['is_active']:
                        return row  # {'is_active': .., 'username': ...}
            return None
